/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include <stm32_nor_flash.h>
#include <mx25um51245g.h>
#include <stm32_common.h>
#include <stm32_errno.h>
/* includes (standard library, system) ---------------------------------------*/
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <hal_mtd.h>
#include <hal_it.h>

/* defines -------------------------------------------------------------------*/

typedef enum {
    OSPI_ACCESS_NONE = 0, /*!<  Instance not initialized,              */
    OSPI_ACCESS_INDIRECT, /*!<  Instance use indirect mode access      */
    OSPI_ACCESS_MMP       /*!<  Instance use Memory Mapped Mode read   */
} OSPI_Access_t;

typedef struct {
    uint32_t MemorySize;
    uint32_t ClockPrescaler;
    uint32_t SampleShifting;
    uint32_t TransferRate;
} MX_OSPI_InitTypeDef;

#define BSP_OSPI_NOR_Info_t      MX25UM51245G_Info_t
#define BSP_OSPI_NOR_Interface_t MX25UM51245G_Interface_t
#define BSP_OSPI_NOR_Transfer_t  MX25UM51245G_Transfer_t
#define BSP_OSPI_NOR_Erase_t     MX25UM51245G_Erase_t

typedef struct {
    OSPI_Access_t IsInitialized;            /*!<  Instance access Flash method     */
    BSP_OSPI_NOR_Interface_t InterfaceMode; /*!<  Flash Interface mode of Instance */
    BSP_OSPI_NOR_Transfer_t TransferRate;   /*!<  Flash Transfer mode of Instance  */
} OSPI_NOR_Ctx_t;

typedef struct {
    BSP_OSPI_NOR_Interface_t InterfaceMode; /*!<  Current Flash Interface mode */
    BSP_OSPI_NOR_Transfer_t TransferRate;   /*!<  Current Flash Transfer rate  */
} BSP_OSPI_NOR_Init_t;

/* Definition for OSPI clock resources */
#define OSPI_CLK_ENABLE()  __HAL_RCC_OSPI1_CLK_ENABLE()
#define OSPI_CLK_DISABLE() __HAL_RCC_OSPI1_CLK_DISABLE()

#define OSPI_CLK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
#define OSPI_DQS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define OSPI_CS_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOA_CLK_ENABLE()
#define OSPI_D0_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
#define OSPI_D1_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
#define OSPI_D2_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
#define OSPI_D3_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
#define OSPI_D4_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOC_CLK_ENABLE()
#define OSPI_D5_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOC_CLK_ENABLE()
#define OSPI_D6_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOC_CLK_ENABLE()
#define OSPI_D7_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOC_CLK_ENABLE()

#define OSPI_FORCE_RESET()   __HAL_RCC_OSPI1_FORCE_RESET()
#define OSPI_RELEASE_RESET() __HAL_RCC_OSPI1_RELEASE_RESET()

/* Definition for OSPI Pins */
/* OSPI_CLK */
#define OSPI_CLK_PIN       GPIO_PIN_10
#define OSPI_CLK_GPIO_PORT GPIOF
#define OSPI_CLK_PIN_AF    GPIO_AF3_OCTOSPI1
/* OSPI_DQS */
#define OSPI_DQS_PIN       GPIO_PIN_1
#define OSPI_DQS_GPIO_PORT GPIOA
#define OSPI_DQS_PIN_AF    GPIO_AF10_OCTOSPI1
/* OSPI_CS */
#define OSPI_CS_PIN        GPIO_PIN_2
#define OSPI_CS_GPIO_PORT  GPIOA
#define OSPI_CS_PIN_AF     GPIO_AF10_OCTOSPI1
/* OSPI_D0 */
#define OSPI_D0_PIN        GPIO_PIN_8
#define OSPI_D0_GPIO_PORT  GPIOF
#define OSPI_D0_PIN_AF     GPIO_AF10_OCTOSPI1
/* OSPI_D1 */
#define OSPI_D1_PIN        GPIO_PIN_9
#define OSPI_D1_GPIO_PORT  GPIOF
#define OSPI_D1_PIN_AF     GPIO_AF10_OCTOSPI1
/* OSPI_D2 */
#define OSPI_D2_PIN        GPIO_PIN_7
#define OSPI_D2_GPIO_PORT  GPIOF
#define OSPI_D2_PIN_AF     GPIO_AF10_OCTOSPI1
/* OSPI_D3 */
#define OSPI_D3_PIN        GPIO_PIN_6
#define OSPI_D3_GPIO_PORT  GPIOF
#define OSPI_D3_PIN_AF     GPIO_AF10_OCTOSPI1
/* OSPI_D4 */
#define OSPI_D4_PIN        GPIO_PIN_1
#define OSPI_D4_GPIO_PORT  GPIOC
#define OSPI_D4_PIN_AF     GPIO_AF10_OCTOSPI1
/* OSPI_D5 */
#define OSPI_D5_PIN        GPIO_PIN_2
#define OSPI_D5_GPIO_PORT  GPIOC
#define OSPI_D5_PIN_AF     GPIO_AF10_OCTOSPI1
/* OSPI_D6 */
#define OSPI_D6_PIN        GPIO_PIN_3
#define OSPI_D6_GPIO_PORT  GPIOC
#define OSPI_D6_PIN_AF     GPIO_AF10_OCTOSPI1
/* OSPI_D7 */
#define OSPI_D7_PIN        GPIO_PIN_0
#define OSPI_D7_GPIO_PORT  GPIOC
#define OSPI_D7_PIN_AF     GPIO_AF3_OCTOSPI1

#define OSPI_NOR_INSTANCES_NUMBER 1U

/* Definition for OSPI modes */
/* 1 Cmd Line, 1 Address Line and 1 Data Line    */
#define BSP_OSPI_NOR_SPI_MODE MX25UM51245G_SPI_MODE
/* 8 Cmd Lines, 8 Address Lines and 8 Data Lines */
#define BSP_OSPI_NOR_OPI_MODE MX25UM51245G_OPI_MODE

/* Definition for OSPI transfer rates */
/* Single Transfer Rate */
#define BSP_OSPI_NOR_STR_TRANSFER MX25UM51245G_STR_TRANSFER
/* Double Transfer Rate */
#define BSP_OSPI_NOR_DTR_TRANSFER MX25UM51245G_DTR_TRANSFER

/* OSPI erase types */
#define BSP_OSPI_NOR_ERASE_4K   MX25UM51245G_ERASE_4K
#define BSP_OSPI_NOR_ERASE_64K  MX25UM51245G_ERASE_64K
#define BSP_OSPI_NOR_ERASE_CHIP MX25UM51245G_ERASE_BULK

/* OSPI block sizes */
#define BSP_OSPI_NOR_BLOCK_4K  MX25UM51245G_SUBSECTOR_4K
#define BSP_OSPI_NOR_BLOCK_64K MX25UM51245G_SECTOR_64K

/* typedefs ------------------------------------------------------------------*/

typedef struct nor_flash_dev {
    hal_mtd_t mtd;
    XSPI_HandleTypeDef handle;
    OSPI_NOR_Ctx_t ctx;
} nor_flash_dev_t;

/* macro ---------------------------------------------------------------------*/
/* variables (extern) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/

static nor_flash_dev_t nor_flash_dev;

/* variables (global) --------------------------------------------------------*/
/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

static void OSPI_NOR_MspInit(XSPI_HandleTypeDef *hospi)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    /* hospi unused argument(s) compilation warning */
    UNUSED(hospi);

    /* Enable the OctoSPI memory interface clock */
    OSPI_CLK_ENABLE();

    __HAL_RCC_PWR_CLK_ENABLE();
    /* Enable VDDIO2 supply */
    HAL_PWREx_EnableVddIO2();

    /* Reset the OctoSPI memory interface */
    OSPI_FORCE_RESET();
    OSPI_RELEASE_RESET();

    /* Enable GPIO clocks */
    OSPI_CLK_GPIO_CLK_ENABLE();
    OSPI_DQS_GPIO_CLK_ENABLE();
    OSPI_CS_GPIO_CLK_ENABLE();
    OSPI_D0_GPIO_CLK_ENABLE();
    OSPI_D1_GPIO_CLK_ENABLE();
    OSPI_D2_GPIO_CLK_ENABLE();
    OSPI_D3_GPIO_CLK_ENABLE();
    OSPI_D4_GPIO_CLK_ENABLE();
    OSPI_D5_GPIO_CLK_ENABLE();
    OSPI_D6_GPIO_CLK_ENABLE();
    OSPI_D7_GPIO_CLK_ENABLE();

    /* Enable HSLV GPIOs */
    HAL_GPIO_EnableHighSPeedLowVoltage(OSPI_CLK_GPIO_PORT, OSPI_CLK_PIN);
    HAL_GPIO_EnableHighSPeedLowVoltage(OSPI_CS_GPIO_PORT, OSPI_CS_PIN);
    HAL_GPIO_EnableHighSPeedLowVoltage(OSPI_DQS_GPIO_PORT, OSPI_DQS_PIN);
    HAL_GPIO_EnableHighSPeedLowVoltage(OSPI_D0_GPIO_PORT, OSPI_D0_PIN);
    HAL_GPIO_EnableHighSPeedLowVoltage(OSPI_D1_GPIO_PORT, OSPI_D1_PIN);
    HAL_GPIO_EnableHighSPeedLowVoltage(OSPI_D2_GPIO_PORT, OSPI_D2_PIN);
    HAL_GPIO_EnableHighSPeedLowVoltage(OSPI_D3_GPIO_PORT, OSPI_D3_PIN);
    HAL_GPIO_EnableHighSPeedLowVoltage(OSPI_D4_GPIO_PORT, OSPI_D4_PIN);
    HAL_GPIO_EnableHighSPeedLowVoltage(OSPI_D5_GPIO_PORT, OSPI_D5_PIN);
    HAL_GPIO_EnableHighSPeedLowVoltage(OSPI_D6_GPIO_PORT, OSPI_D6_PIN);
    HAL_GPIO_EnableHighSPeedLowVoltage(OSPI_D7_GPIO_PORT, OSPI_D7_PIN);

    /* OctoSPI CS GPIO pin configuration  */
    GPIO_InitStruct.Pin = OSPI_CS_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = OSPI_CS_PIN_AF;
    HAL_GPIO_Init(OSPI_CS_GPIO_PORT, &GPIO_InitStruct);

    /* OctoSPI CLK GPIO pin configuration  */
    GPIO_InitStruct.Pin = OSPI_CLK_PIN;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Alternate = OSPI_CLK_PIN_AF;
    HAL_GPIO_Init(OSPI_CLK_GPIO_PORT, &GPIO_InitStruct);

    /* OctoSPI D0 GPIO pin configuration  */
    GPIO_InitStruct.Pin = OSPI_D0_PIN;
    GPIO_InitStruct.Alternate = OSPI_D0_PIN_AF;
    HAL_GPIO_Init(OSPI_D0_GPIO_PORT, &GPIO_InitStruct);

    /* OctoSPI D1 GPIO pin configuration  */
    GPIO_InitStruct.Pin = OSPI_D1_PIN;
    GPIO_InitStruct.Alternate = OSPI_D1_PIN_AF;
    HAL_GPIO_Init(OSPI_D1_GPIO_PORT, &GPIO_InitStruct);

    /* OctoSPI D2 GPIO pin configuration  */
    GPIO_InitStruct.Pin = OSPI_D2_PIN;
    GPIO_InitStruct.Alternate = OSPI_D2_PIN_AF;
    HAL_GPIO_Init(OSPI_D2_GPIO_PORT, &GPIO_InitStruct);

    /* OctoSPI D3 GPIO pin configuration  */
    GPIO_InitStruct.Pin = OSPI_D3_PIN;
    GPIO_InitStruct.Alternate = OSPI_D3_PIN_AF;
    HAL_GPIO_Init(OSPI_D3_GPIO_PORT, &GPIO_InitStruct);

    /* OctoSPI D4 GPIO pin configuration  */
    GPIO_InitStruct.Pin = OSPI_D4_PIN;
    GPIO_InitStruct.Alternate = OSPI_D4_PIN_AF;
    HAL_GPIO_Init(OSPI_D4_GPIO_PORT, &GPIO_InitStruct);

    /* OctoSPI D5 GPIO pin configuration  */
    GPIO_InitStruct.Pin = OSPI_D5_PIN;
    GPIO_InitStruct.Alternate = OSPI_D5_PIN_AF;
    HAL_GPIO_Init(OSPI_D5_GPIO_PORT, &GPIO_InitStruct);

    /* OctoSPI D6 GPIO pin configuration  */
    GPIO_InitStruct.Pin = OSPI_D6_PIN;
    GPIO_InitStruct.Alternate = OSPI_D6_PIN_AF;
    HAL_GPIO_Init(OSPI_D6_GPIO_PORT, &GPIO_InitStruct);

    /* OctoSPI D7 GPIO pin configuration  */
    GPIO_InitStruct.Pin = OSPI_D7_PIN;
    GPIO_InitStruct.Alternate = OSPI_D7_PIN_AF;
    HAL_GPIO_Init(OSPI_D7_GPIO_PORT, &GPIO_InitStruct);

    /* OctoSPI DQS GPIO pin configuration  */
    GPIO_InitStruct.Pin = OSPI_DQS_PIN;
    GPIO_InitStruct.Alternate = OSPI_DQS_PIN_AF;
    HAL_GPIO_Init(OSPI_DQS_GPIO_PORT, &GPIO_InitStruct);

    /* Configure the NVIC for OSPI */
    /* NVIC configuration for OSPI interrupt */
    HAL_NVIC_SetPriority(OCTOSPI1_IRQn, 0x0F, 0);
    HAL_NVIC_EnableIRQ(OCTOSPI1_IRQn);
}

static void OSPI_NOR_MspDeInit(XSPI_HandleTypeDef *hospi)
{
    /* hospi unused argument(s) compilation warning */
    UNUSED(hospi);

    /* Reset the OctoSPI memory interface */
    OSPI_FORCE_RESET();
    OSPI_RELEASE_RESET();

    /* Disable the OctoSPI memory interface clock */
    OSPI_CLK_DISABLE();
}

static int32_t OSPI_NOR_ResetMemory(nor_flash_dev_t *dev)
{
    XSPI_HandleTypeDef *hospi = &dev->handle;
    OSPI_NOR_Ctx_t *ctx = &dev->ctx;

    if (MX25UM51245G_ResetEnable(hospi, BSP_OSPI_NOR_SPI_MODE, BSP_OSPI_NOR_STR_TRANSFER) !=
        MX25UM51245G_OK) {
        return -1;
    }
    if (MX25UM51245G_ResetMemory(hospi, BSP_OSPI_NOR_SPI_MODE, BSP_OSPI_NOR_STR_TRANSFER) !=
        MX25UM51245G_OK) {
        return -2;
    }
    if (MX25UM51245G_ResetEnable(hospi, BSP_OSPI_NOR_OPI_MODE, BSP_OSPI_NOR_STR_TRANSFER) !=
        MX25UM51245G_OK) {
        return -3;
    }
    if (MX25UM51245G_ResetMemory(hospi, BSP_OSPI_NOR_OPI_MODE, BSP_OSPI_NOR_STR_TRANSFER) !=
        MX25UM51245G_OK) {
        return -4;
    }
    if (MX25UM51245G_ResetEnable(hospi, BSP_OSPI_NOR_OPI_MODE, BSP_OSPI_NOR_DTR_TRANSFER) !=
        MX25UM51245G_OK) {
        return -5;
    }
    if (MX25UM51245G_ResetMemory(hospi, BSP_OSPI_NOR_OPI_MODE, BSP_OSPI_NOR_DTR_TRANSFER) !=
        MX25UM51245G_OK) {
        return -6;
    }
    /* After reset S/W setting to indirect access  */
    ctx->IsInitialized = OSPI_ACCESS_INDIRECT;
    /* After reset H/W back to SPI mode by default */
    ctx->InterfaceMode = BSP_OSPI_NOR_SPI_MODE;
    /* After reset S/W setting to STR mode         */
    ctx->TransferRate = BSP_OSPI_NOR_STR_TRANSFER;

    /* After SWreset CMD, wait in case SWReset occurred during erase operation */
    HAL_Delay(MX25UM51245G_RESET_MAX_TIME);

    /* Return BSP status */
    return 0;
}

__weak HAL_StatusTypeDef MX_OSPI_NOR_Init(XSPI_HandleTypeDef *hospi, MX_OSPI_InitTypeDef *Init)
{
    /* OctoSPI initialization */
    hospi->Instance = OCTOSPI1;

    hospi->Init.FifoThresholdByte = 4;
    hospi->Init.MemoryMode = HAL_XSPI_SINGLE_MEM;
    hospi->Init.MemorySize = Init->MemorySize; /* 512 MBits */
    hospi->Init.ChipSelectHighTimeCycle = 2;
    hospi->Init.FreeRunningClock = HAL_XSPI_FREERUNCLK_DISABLE;
    hospi->Init.ClockMode = HAL_XSPI_CLOCK_MODE_0;
    hospi->Init.WrapSize = HAL_XSPI_WRAP_NOT_SUPPORTED;
    hospi->Init.ClockPrescaler = Init->ClockPrescaler;
    hospi->Init.SampleShifting = Init->SampleShifting;
    hospi->Init.ChipSelectBoundary = 0;
    hospi->Init.DelayBlockBypass = HAL_XSPI_DELAY_BLOCK_ON;

    if (Init->TransferRate == (uint32_t)BSP_OSPI_NOR_DTR_TRANSFER) {
        hospi->Init.MemoryType = HAL_XSPI_MEMTYPE_MACRONIX;
        hospi->Init.DelayHoldQuarterCycle = HAL_XSPI_DHQC_ENABLE;
    } else {
        hospi->Init.MemoryType = HAL_XSPI_MEMTYPE_MICRON;
        hospi->Init.DelayHoldQuarterCycle = HAL_XSPI_DHQC_DISABLE;
    }

    return HAL_XSPI_Init(hospi);
}

static int32_t OSPI_DLYB_Enable(XSPI_HandleTypeDef *hospi)
{
    LL_DLYB_CfgTypeDef dlyb_cfg, dlyb_cfg_test;
    uint32_t div_value = 4;

    /* Delay block configuration ------------------------------------------------ */
    if (HAL_XSPI_DLYB_GetClockPeriod(hospi, &dlyb_cfg) != HAL_OK) {
        return -1;
    }

    /* PhaseSel is divided by 4 (emperic value)*/
    dlyb_cfg.PhaseSel /= div_value;

    /* save the present configuration for check*/
    dlyb_cfg_test = dlyb_cfg;

    /*set delay block configuration*/
    if (HAL_XSPI_DLYB_SetConfig(hospi, &dlyb_cfg) != HAL_OK) {
        return -2;
    }

    /*check the set value*/
    if (HAL_XSPI_DLYB_GetConfig(hospi, &dlyb_cfg) != HAL_OK) {
        return -3;
    }

    if ((dlyb_cfg.PhaseSel != dlyb_cfg_test.PhaseSel) || (dlyb_cfg.Units != dlyb_cfg_test.Units)) {
        return -4;
    }

    /* Return BSP status */
    return 0;
}

static int32_t OSPI_NOR_EnterDOPIMode(nor_flash_dev_t *dev)
{
    XSPI_HandleTypeDef *hospi = &dev->handle;
    OSPI_NOR_Ctx_t *ctx = &dev->ctx;
    int32_t ret;
    uint8_t reg[2];

    /* Enable write operations */
    if (MX25UM51245G_WriteEnable(hospi, ctx->InterfaceMode, ctx->TransferRate) != MX25UM51245G_OK) {
        ret = BSP_ERROR_COMPONENT_FAILURE;
    }
    /* Write Configuration register 2 (with new dummy cycles) */
    else if (MX25UM51245G_WriteCfg2Register(hospi, ctx->InterfaceMode, ctx->TransferRate,
                                            MX25UM51245G_CR2_REG3_ADDR,
                                            MX25UM51245G_CR2_DC_6_CYCLES) != MX25UM51245G_OK) {
        ret = BSP_ERROR_COMPONENT_FAILURE;
    }
    /* Enable write operations */
    else if (MX25UM51245G_WriteEnable(hospi, ctx->InterfaceMode, ctx->TransferRate) !=
             MX25UM51245G_OK) {
        ret = BSP_ERROR_COMPONENT_FAILURE;
    }
    /* Write Configuration register 2 (with Octal I/O SPI protocol) */
    else if (MX25UM51245G_WriteCfg2Register(hospi, ctx->InterfaceMode, ctx->TransferRate,
                                            MX25UM51245G_CR2_REG1_ADDR,
                                            MX25UM51245G_CR2_DOPI) != MX25UM51245G_OK) {
        ret = BSP_ERROR_COMPONENT_FAILURE;
    } else {
        /* Wait that the configuration is effective and check that memory is ready */
        HAL_Delay(MX25UM51245G_WRITE_REG_MAX_TIME);

        /* Reconfigure the memory type of the peripheral */
        hospi->Init.MemoryType = HAL_XSPI_MEMTYPE_MACRONIX;
        hospi->Init.DelayHoldQuarterCycle = HAL_XSPI_DHQC_ENABLE;
        if (HAL_XSPI_Init(hospi) != HAL_OK) {
            ret = BSP_ERROR_PERIPH_FAILURE;
        }
        /* Check Flash busy ? */
        else if (MX25UM51245G_AutoPollingMemReady(hospi, BSP_OSPI_NOR_OPI_MODE,
                                                  BSP_OSPI_NOR_DTR_TRANSFER) != MX25UM51245G_OK) {
            ret = BSP_ERROR_COMPONENT_FAILURE;
        }
        /* Check the configuration has been correctly done */
        else if (MX25UM51245G_ReadCfg2Register(
                     hospi, BSP_OSPI_NOR_OPI_MODE, BSP_OSPI_NOR_DTR_TRANSFER,
                     MX25UM51245G_CR2_REG1_ADDR, reg) != MX25UM51245G_OK) {
            ret = BSP_ERROR_COMPONENT_FAILURE;
        } else if (reg[0] != MX25UM51245G_CR2_DOPI) {
            ret = BSP_ERROR_COMPONENT_FAILURE;
        } else {
            ret = BSP_ERROR_NONE;
        }
    }

    /* Return BSP status */
    return ret;
}

/**
 * @brief  This function enables the octal STR mode of the memory.
 * @param  Instance  OSPI instance
 * @retval BSP status
 */
static int32_t OSPI_NOR_EnterSOPIMode(nor_flash_dev_t *dev)
{
    XSPI_HandleTypeDef *hospi = &dev->handle;
    OSPI_NOR_Ctx_t *ctx = &dev->ctx;
    int32_t ret;
    uint8_t reg[2];

    /* Enable write operations */
    if (MX25UM51245G_WriteEnable(hospi, ctx->InterfaceMode, ctx->TransferRate) != MX25UM51245G_OK) {
        ret = BSP_ERROR_COMPONENT_FAILURE;
    }
    /* Write Configuration register 2 (with new dummy cycles) */
    else if (MX25UM51245G_WriteCfg2Register(hospi, ctx->InterfaceMode, ctx->TransferRate,
                                            MX25UM51245G_CR2_REG3_ADDR,
                                            MX25UM51245G_CR2_DC_6_CYCLES) != MX25UM51245G_OK) {
        ret = BSP_ERROR_COMPONENT_FAILURE;
    }
    /* Enable write operations */
    else if (MX25UM51245G_WriteEnable(hospi, ctx->InterfaceMode, ctx->TransferRate) !=
             MX25UM51245G_OK) {
        ret = BSP_ERROR_COMPONENT_FAILURE;
    }
    /* Write Configuration register 2 (with Octal I/O SPI protocol) */
    else if (MX25UM51245G_WriteCfg2Register(hospi, ctx->InterfaceMode, ctx->TransferRate,
                                            MX25UM51245G_CR2_REG1_ADDR,
                                            MX25UM51245G_CR2_SOPI) != MX25UM51245G_OK) {
        ret = BSP_ERROR_COMPONENT_FAILURE;
    } else {
        /* Wait that the configuration is effective and check that memory is ready */
        HAL_Delay(MX25UM51245G_WRITE_REG_MAX_TIME);

        /* Check Flash busy ? */
        if (MX25UM51245G_AutoPollingMemReady(hospi, BSP_OSPI_NOR_OPI_MODE,
                                             BSP_OSPI_NOR_STR_TRANSFER) != MX25UM51245G_OK) {
            ret = BSP_ERROR_COMPONENT_FAILURE;
        }
        /* Check the configuration has been correctly done */
        else if (MX25UM51245G_ReadCfg2Register(
                     hospi, BSP_OSPI_NOR_OPI_MODE, BSP_OSPI_NOR_STR_TRANSFER,
                     MX25UM51245G_CR2_REG1_ADDR, reg) != MX25UM51245G_OK) {
            ret = BSP_ERROR_COMPONENT_FAILURE;
        } else if (reg[0] != MX25UM51245G_CR2_SOPI) {
            ret = BSP_ERROR_COMPONENT_FAILURE;
        } else {
            ret = BSP_ERROR_NONE;
        }
    }

    /* Return BSP status */
    return ret;
}

static int32_t OSPI_NOR_ExitOPIMode(nor_flash_dev_t *dev)
{
    XSPI_HandleTypeDef *hospi = &dev->handle;
    OSPI_NOR_Ctx_t *ctx = &dev->ctx;
    int32_t ret = BSP_ERROR_NONE;
    uint8_t reg[2];

    /* Enable write operations */
    if (MX25UM51245G_WriteEnable(hospi, ctx->InterfaceMode, ctx->TransferRate) != MX25UM51245G_OK) {
        ret = BSP_ERROR_COMPONENT_FAILURE;
    } else {
        /* Write Configuration register 2 (with SPI protocol) */
        reg[0] = 0;
        reg[1] = 0;
        if (MX25UM51245G_WriteCfg2Register(hospi, ctx->InterfaceMode, ctx->TransferRate,
                                           MX25UM51245G_CR2_REG1_ADDR, reg[0]) != MX25UM51245G_OK) {
            ret = BSP_ERROR_COMPONENT_FAILURE;
        } else {
            /* Wait that the configuration is effective and check that memory is ready */
            HAL_Delay(MX25UM51245G_WRITE_REG_MAX_TIME);

            if (ctx->TransferRate == BSP_OSPI_NOR_DTR_TRANSFER) {
                /* Reconfigure the memory type of the peripheral */
                hospi->Init.MemoryType = HAL_XSPI_MEMTYPE_MICRON;
                hospi->Init.DelayHoldQuarterCycle = HAL_XSPI_DHQC_DISABLE;
                if (HAL_XSPI_Init(hospi) != HAL_OK) {
                    ret = BSP_ERROR_PERIPH_FAILURE;
                }
            }

            if (ret == BSP_ERROR_NONE) {
                /* Check Flash busy ? */
                if (MX25UM51245G_AutoPollingMemReady(hospi, BSP_OSPI_NOR_SPI_MODE,
                                                     BSP_OSPI_NOR_STR_TRANSFER) !=
                    MX25UM51245G_OK) {
                    ret = BSP_ERROR_COMPONENT_FAILURE;
                }
                /* Check the configuration has been correctly done */
                else if (MX25UM51245G_ReadCfg2Register(
                             hospi, BSP_OSPI_NOR_SPI_MODE, BSP_OSPI_NOR_STR_TRANSFER,
                             MX25UM51245G_CR2_REG1_ADDR, reg) != MX25UM51245G_OK) {
                    ret = BSP_ERROR_COMPONENT_FAILURE;
                } else if (reg[0] != 0U) {
                    ret = BSP_ERROR_COMPONENT_FAILURE;
                } else {
                    /* Nothing to do */
                }
            }
        }
    }

    /* Return BSP status */
    return ret;
}

static int32_t BSP_OSPI_NOR_ConfigFlash(nor_flash_dev_t *dev, BSP_OSPI_NOR_Interface_t Mode,
                                        BSP_OSPI_NOR_Transfer_t Rate)
{
    OSPI_NOR_Ctx_t *ctx = &dev->ctx;
    int32_t ret = BSP_ERROR_NONE;

    /* Check if MMP mode locked ************************************************/
    if (ctx->IsInitialized == OSPI_ACCESS_MMP) {
        return BSP_ERROR_WRONG_PARAM;
    }
    /* Setup Flash interface ***************************************************/
    switch (ctx->InterfaceMode) {
    case BSP_OSPI_NOR_OPI_MODE: /* 8-8-8 commands */
        if ((Mode != BSP_OSPI_NOR_OPI_MODE) || (Rate != ctx->TransferRate)) {
            /* Exit OPI mode */
            ret = OSPI_NOR_ExitOPIMode(dev);

            if ((ret == BSP_ERROR_NONE) && (Mode == BSP_OSPI_NOR_OPI_MODE)) {
                if (ctx->TransferRate == BSP_OSPI_NOR_STR_TRANSFER) {
                    /* Enter DTR OPI mode */
                    ret = OSPI_NOR_EnterDOPIMode(dev);
                } else {
                    /* Enter STR OPI mode */
                    ret = OSPI_NOR_EnterSOPIMode(dev);
                }
            }
        }
        break;

    case BSP_OSPI_NOR_SPI_MODE: /* 1-1-1 commands, Power on H/W default setting */
    default:
        if (Mode == BSP_OSPI_NOR_OPI_MODE) {
            if (Rate == BSP_OSPI_NOR_STR_TRANSFER) {
                /* Enter STR OPI mode */
                ret = OSPI_NOR_EnterSOPIMode(dev);
            } else {
                /* Enter DTR OPI mode */
                ret = OSPI_NOR_EnterDOPIMode(dev);
            }
        }
        break;
    }

    /* Update OSPI context if all operations are well done */
    if (ret == BSP_ERROR_NONE) {
        /* Update current status parameter *****************************************/
        ctx->IsInitialized = OSPI_ACCESS_INDIRECT;
        ctx->InterfaceMode = Mode;
        ctx->TransferRate = Rate;
    }

    /* Return BSP status */
    return ret;
}

static int32_t BSP_OSPI_NOR_EnableMemoryMappedMode(nor_flash_dev_t *dev)
{
    XSPI_HandleTypeDef *hospi = &dev->handle;
    OSPI_NOR_Ctx_t *ctx = &dev->ctx;
    int32_t ret = BSP_ERROR_NONE;

    if (ctx->TransferRate == BSP_OSPI_NOR_STR_TRANSFER) {
        if (MX25UM51245G_EnableMemoryMappedModeSTR(hospi, ctx->InterfaceMode,
                                                   MX25UM51245G_4BYTES_SIZE) != MX25UM51245G_OK) {
            ret = BSP_ERROR_COMPONENT_FAILURE;
        } else {
            /* Update OSPI context if all operations are well done */
            ctx->IsInitialized = OSPI_ACCESS_MMP;
        }
    } else {
        if (MX25UM51245G_EnableMemoryMappedModeDTR(hospi, ctx->InterfaceMode) != MX25UM51245G_OK) {
            ret = BSP_ERROR_COMPONENT_FAILURE;
        } else {
            /* Update OSPI context if all operations are well done */
            ctx->IsInitialized = OSPI_ACCESS_MMP;
        }
    }

    /* Return BSP status */
    return ret;
}

static int32_t BSP_OSPI_NOR_DisableMemoryMappedMode(nor_flash_dev_t *dev)
{
    XSPI_HandleTypeDef *hospi = &dev->handle;
    OSPI_NOR_Ctx_t *ctx = &dev->ctx;
    int32_t ret = BSP_ERROR_NONE;
    uint32_t tickstart = HAL_GetTick();

    if (ctx->IsInitialized != OSPI_ACCESS_MMP) {
        ret = BSP_ERROR_OSPI_MMP_UNLOCK_FAILURE;
    } /* Abort MMP back to indirect mode */
    else if (HAL_XSPI_Abort(hospi) != HAL_OK) {
        ret = BSP_ERROR_PERIPH_FAILURE;
    } else {
        /* Wait until flag is in expected state */
        while ((HAL_XSPI_GET_FLAG(hospi, HAL_XSPI_FLAG_BUSY)) != RESET) {
            /* Check for the Timeout */
            if (((HAL_GetTick() - tickstart) > hospi->Timeout) || (hospi->Timeout == 0U)) {
                ret = BSP_ERROR_PERIPH_FAILURE;
                break;
            }
        }

        if (ret == BSP_ERROR_NONE) {
            /* Configure CR register with functional mode as indirect mode*/
            MODIFY_REG(hospi->Instance->CR, (OCTOSPI_CR_FMODE), 0U);
            ctx->IsInitialized = OSPI_ACCESS_INDIRECT;
        }
    }

    /* Return BSP status */
    return ret;
}

static int __stm32_nor_flash_init(nor_flash_dev_t *dev, BSP_OSPI_NOR_Init_t *Init)
{
    XSPI_HandleTypeDef *hospi = &dev->handle;
    OSPI_NOR_Ctx_t *ctx = &dev->ctx;
    BSP_OSPI_NOR_Info_t pInfo;
    MX_OSPI_InitTypeDef ospi_init;

    /* Msp OSPI initialization */
    OSPI_NOR_MspInit(hospi);

    /* Get Flash information of one memory */
    (void)MX25UM51245G_GetFlashInfo(&pInfo);

    /* Fill config structure */
    ospi_init.ClockPrescaler = 1;
    ospi_init.MemorySize = (uint32_t)POSITION_VAL((uint32_t)pInfo.FlashSize);
    ospi_init.SampleShifting = HAL_XSPI_SAMPLE_SHIFT_NONE;
    ospi_init.TransferRate = (uint32_t)Init->TransferRate;

    /* STM32 OSPI interface initialization */
    if (MX_OSPI_NOR_Init(hospi, &ospi_init) != HAL_OK) {
        return BSP_ERROR_PERIPH_FAILURE;
    }
    /* OSPI Delay Block enable */
    if (OSPI_DLYB_Enable(hospi) != 0) {
        return BSP_ERROR_COMPONENT_FAILURE;
    }
    /* OSPI memory reset */
    if (OSPI_NOR_ResetMemory(dev) != 0) {
        return BSP_ERROR_COMPONENT_FAILURE;
    }
    /* Check if memory is ready */
    if (MX25UM51245G_AutoPollingMemReady(hospi, ctx->InterfaceMode, ctx->TransferRate) !=
        MX25UM51245G_OK) {
        return BSP_ERROR_COMPONENT_FAILURE;
    }
    /* Configure the memory */
    if (BSP_OSPI_NOR_ConfigFlash(dev, Init->InterfaceMode, Init->TransferRate) != 0) {
        return BSP_ERROR_COMPONENT_FAILURE;
    }

    return 0;
}

static int __nor_flash_erase_block(hal_mtd_t *mtd, size_t startblock, size_t nblocks)
{
    nor_flash_dev_t *dev = container_of(mtd, nor_flash_dev_t, mtd);
    XSPI_HandleTypeDef *hospi = &dev->handle;
    OSPI_NOR_Ctx_t *ctx = &dev->ctx;

    /* Check Flash busy ? */
    if (MX25UM51245G_AutoPollingMemReady(hospi, ctx->InterfaceMode, ctx->TransferRate) !=
        MX25UM51245G_OK) {
        return -1;
    }
    /* Enable write operations */
    if (MX25UM51245G_WriteEnable(hospi, ctx->InterfaceMode, ctx->TransferRate) != MX25UM51245G_OK) {
        return -1;
    }
    /* Issue Block Erase command */
    if (MX25UM51245G_BlockErase(hospi, ctx->InterfaceMode, ctx->TransferRate,
                                MX25UM51245G_4BYTES_SIZE, startblock, nblocks) != MX25UM51245G_OK) {
        return -1;
    }
    return 0;
}

static int __nor_flash_read(hal_mtd_t *mtd, size_t pos, size_t nbytes, uint8_t *buf)
{
    nor_flash_dev_t *dev = container_of(mtd, nor_flash_dev_t, mtd);
    XSPI_HandleTypeDef *hospi = &dev->handle;
    OSPI_NOR_Ctx_t *ctx = &dev->ctx;

    if (ctx->TransferRate == BSP_OSPI_NOR_STR_TRANSFER) {
        if (MX25UM51245G_ReadSTR(hospi, ctx->InterfaceMode, MX25UM51245G_4BYTES_SIZE, buf, pos,
                                 nbytes) != MX25UM51245G_OK) {
            return -1;
        }
    } else {
        if (MX25UM51245G_ReadDTR(hospi, buf, pos, nbytes) != MX25UM51245G_OK) {
            return -2;
        }
    }

    return 0;
}

static int __nor_flash_write(hal_mtd_t *mtd, size_t pos, size_t nbytes, const uint8_t *buf)
{
    nor_flash_dev_t *dev = container_of(mtd, nor_flash_dev_t, mtd);
    XSPI_HandleTypeDef *hospi = &dev->handle;
    OSPI_NOR_Ctx_t *ctx = &dev->ctx;

    /* Check if Flash busy ? */
    if (MX25UM51245G_AutoPollingMemReady(hospi, ctx->InterfaceMode, ctx->TransferRate) !=
        MX25UM51245G_OK) {
        return -1;
    }
    /* Enable write operations */
    if (MX25UM51245G_WriteEnable(hospi, ctx->InterfaceMode, ctx->TransferRate) != MX25UM51245G_OK) {
        return -2;
    }
    if (ctx->TransferRate == BSP_OSPI_NOR_STR_TRANSFER) {
        /* Issue page program command */
        if (MX25UM51245G_PageProgram(hospi, ctx->InterfaceMode, MX25UM51245G_4BYTES_SIZE,
                                     (uint8_t *)buf, pos, nbytes) != MX25UM51245G_OK) {
            return -3;
        }
    } else {
        /* Issue page program command */
        if (MX25UM51245G_PageProgramDTR(hospi, (uint8_t *)buf, pos, nbytes) != MX25UM51245G_OK) {
            return -4;
        }
    }

    /* Configure automatic polling mode to wait for end of program */
    if (MX25UM51245G_AutoPollingMemReady(hospi, ctx->InterfaceMode, ctx->TransferRate) !=
        MX25UM51245G_OK) {
        return -5;
    }

    /* Return BSP status */
    return 0;
}

static int __nor_flash_ioctl(hal_mtd_t *mtd, int cmd, va_list valist)
{
    nor_flash_dev_t *dev = container_of(mtd, nor_flash_dev_t, mtd);
    int ret = -ENOSYS;

    switch (cmd) {
    case NOR_FLASH_CMD_MEMORY_MAPPED: {
        size_t flag = va_arg(valist, size_t);
        if (flag == 0) {
            return BSP_OSPI_NOR_DisableMemoryMappedMode(dev);
        } else {
            return BSP_OSPI_NOR_EnableMemoryMappedMode(dev);
        }
    }
    default:
        break;
    }

    return (ret == HAL_OK) ? 0 : -1;
}

static const hal_mtd_methods_t nor_flash_methods = {
    .erase_block = __nor_flash_erase_block,
    .read = __nor_flash_read,
    .write = __nor_flash_write,
    .ioctl = __nor_flash_ioctl,
};

static int __nor_flash_open(hal_dev_t *dev, int oflag, va_list valist)
{
    nor_flash_dev_t *nor_flash = (nor_flash_dev_t *)dev;

    BSP_OSPI_NOR_Init_t sOSPI_NOR_Init;

    sOSPI_NOR_Init.InterfaceMode = BSP_OSPI_NOR_SPI_MODE;
    sOSPI_NOR_Init.TransferRate = BSP_OSPI_NOR_STR_TRANSFER;

    if (__stm32_nor_flash_init(nor_flash, &sOSPI_NOR_Init) != 0) {
        return -1;
    }

    return 0;
}

static int __nor_flash_close(hal_dev_t *dev)
{
    nor_flash_dev_t *nor_flash = (nor_flash_dev_t *)dev;
    XSPI_HandleTypeDef *hospi = &nor_flash->handle;

    if (BSP_OSPI_NOR_DisableMemoryMappedMode(nor_flash) != BSP_ERROR_NONE) {
        return -1;
    }
    OSPI_NOR_MspDeInit(hospi);
    if (HAL_XSPI_DeInit(hospi) != HAL_OK) {
        return -2;
    }

    return 0;
}

static hal_dev_ops_t dev_ops = {
    .open = __nor_flash_open,
    .close = __nor_flash_close,
};

static int stm32_nor_flash_init(void)
{
    nor_flash_dev.mtd.page_size = MX25UM51245G_PAGE_SIZE;
    nor_flash_dev.mtd.total_size = MX25UM51245G_FLASH_SIZE;
    if (hal_mtd_register(&nor_flash_dev.mtd, "nor_flash", &nor_flash_methods, 0) != 0) {
        return -1;
    }
    if (hal_dev_init((hal_dev_t *)&nor_flash_dev, &dev_ops) != 0) {
        return -2;
    }
    return 0;
}

MODULE_INIT(driver, stm32_nor_flash_init);
